package com.cloudfun.campusshare.service.impl;

import cn.afterturn.easypoi.excel.ExcelImportUtil;
import cn.afterturn.easypoi.excel.entity.ImportParams;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import com.cloudfun.campuscontract.RemoteDeviceBlockChainService;
import com.cloudfun.campuscontract.dto.DeviceDTO;
import com.cloudfun.campusshare.common.WechatUserHolder;
import com.cloudfun.campusshare.common.constant.*;
import com.cloudfun.campusshare.common.exception.BusinessException;
import com.cloudfun.campusshare.common.model.dto.DeviceQueryDTO;
import com.cloudfun.campusshare.common.model.vo.*;
import com.cloudfun.campusshare.common.response.BaseResult;
import com.cloudfun.campusshare.common.response.BaseResultFactory;
import com.cloudfun.campusshare.common.response.PageResult;
import com.cloudfun.campusshare.common.security.AppAuthentication;
import com.cloudfun.campusshare.common.security.AuthenticationHolder;
import com.cloudfun.campusshare.common.security.UserAuthentication;
import com.cloudfun.campusshare.controller.FactoryDeviceImportDTO;
import com.cloudfun.campusshare.dao.DeviceDao;
import com.cloudfun.campusshare.device.protocol.AliyunIotService;
import com.cloudfun.campusshare.entity.*;
import com.cloudfun.campusshare.repo.*;
import com.cloudfun.campusshare.service.*;
import com.cloudfun.campusshare.util.BeanCopyUtil;
import com.cloudfun.campusshare.util.QueryUtil;
import com.cloudfun.msglisten.RemoteBloomFilterService;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.TaskExecutor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
import java.io.InputStream;
import java.math.BigInteger;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author linyangping
 * @date 2019/11/5
 */
@Service
@Slf4j
public class DeviceServiceImpl extends CreatorPersistenceService implements DeviceService, InitializingBean {

    @Autowired
    private DeviceEntityRepo deviceEntityRepo;

    @Autowired
    private UserEntityRepo userEntityRepo;

    @Autowired
    private DeviceDao deviceDao;

    @Autowired
    private MessageService messageService;

    @Autowired
    private UserRoleService userRoleService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Reference(timeout = 10_000)
    private RemoteDeviceBlockChainService remoteDeviceBlockChainService;

    @Reference(timeout = 3_000)
    private RemoteBloomFilterService remoteBloomFilterService;

    @Autowired
    private LoanEntityRepo loanEntityRepo;

    @PersistenceContext
    private EntityManager entityManager;

    @Autowired
    private TaskExecutor taskExecutor;

    @Autowired
    private UserRelService userRelService;

    @Resource
    private WashPointEntityRepo washPointEntityRepo;

    @Autowired
    private DeviceModeSetService deviceModeSetService;

    @Resource
    private WashPointConcretePositionEntityRepo washPointConcretePositionEntityRepo;

    @Resource
    private DeviceBrandEntityRepo deviceBrandEntityRepo;

    @Autowired
    private AliyunIOTDeviceOpServiceImpl aliyunIOTDeviceOpService;

    @Resource
    private DeviceAlarmRepo deviceAlarmRepo;

    @Resource
    private DeviceModeSetRepo deviceModeSetRepo;

    @Resource
    private DeviceServiceTimeEntityRepo deviceServiceTimeEntityRepo;

    @Resource
    private OrderEntityRepo orderEntityRepo;

    @Autowired
    private VerifyCodeService verifyCodeService;

    @Autowired
    private AppEntityRepo appEntityRepo;

    @Autowired
    private BindLogRepo bindLogRepo;

    @Autowired
    private SysDictService sysDictService;

    @Autowired
    private AliyunIotService aliyunIotService;

    @Autowired
    private UHomeDeviceOpServiceImpl uHomeDeviceOpService;

    @Autowired
    private PumpWashRelRepo pumpWashRelRepo;

    @Autowired
    private ReplenishService replenishService;

    @Autowired
    private LiquidCapacitySettingRepo liquidCapacitySettingRepo;

    @Override
    protected Class<?> getEntityClass() {
        return DeviceEntity.class;
    }

    @Override
    @Transactional
    public BaseResult<?> merchantBind(DeviceVO deviceVo) {
        // 绑定设备数量检查
        AppAuthentication appAuth = AuthenticationHolder.getAppAuth();
        Optional<AppEntity> appOpt = appEntityRepo.findById(appAuth.getAppId());
        if (!appOpt.isPresent()) {
            return BaseResultFactory.produceResult(Code.NODATA, "应用不存在");
        }
        Integer deviceQuantityLimit = appOpt.get().getDeviceQuantityLimit();
        // 限制为空或者小于等于0，则不做检查
        if (null != deviceQuantityLimit && deviceQuantityLimit > 0) {
            Long appDeviceCount = deviceEntityRepo.countAllByAppId(appOpt.get().getId());
            if (appDeviceCount.intValue() >= deviceQuantityLimit) {
                return BaseResultFactory.produceResult(Code.ERROR, "设备数量超过限制");
            }
        }
        return this.bind(deviceVo);
    }

    @Override
    @Transactional
    public BaseResult<?> bind(DeviceVO deviceVo) {
        DeviceEntity deviceEntity = deviceEntityRepo.findByImei(deviceVo.getImei());
        DeviceType type = DeviceType.getDeviceTypeByName(deviceVo.getDeviceType());
        //当前绑定用户id
        String userId = AuthenticationHolder.getUserId();
        UserEntity merchant = userRelService.getUserMerchant(userId);
        // 第一次添加设备
        if (ObjectUtils.isEmpty(deviceEntity)) {

            deviceEntity = DeviceEntity.builder().
                    deviceName(deviceVo.getDeviceName()).
                    deviceNumber(deviceVo.getDeviceNumber()).
                    devicePlace(deviceVo.getDevicePlace()).
                    address(deviceVo.getAddress()).
                    deviceType(deviceVo.getDeviceType()).
                    qrCode(deviceVo.getQrCode()).
                    imei(deviceVo.getImei()).
                    machineNo(deviceVo.getMachineNo()).
                    userId(merchant.getId()).
                    belongMerchantName(merchant.getEnterpriseName()).
                    bindTime(String.valueOf(System.currentTimeMillis())).
                    online(false).
                    isUsing(true).
                    productType(deviceVo.getProductType()).
                    status(DeviceStatus.NOT_RUNNING).
                    platform(null != deviceVo.getPlatform() ? deviceVo.getPlatform() : DevicePlatform.UHOME).// 平台默认U+
                    build();
            if (null != type) {
                deviceEntity.setProductType(type.getProductType());
            }
            if (null != AuthenticationHolder.getAppAuth()) {
                deviceEntity.setAppId(AuthenticationHolder.getAppAuth().getAppId());
            }
            this.save(deviceEntity);
        } else if (StringUtils.isBlank(deviceEntity.getUserId())) {
            // 解绑后再次绑定
            BeanUtil.copyProperties(deviceVo, deviceEntity, CopyOptions.create().setIgnoreNullValue(true));
            deviceEntity.setUserId(merchant.getId());
            deviceEntity.setBelongMerchantName(merchant.getEnterpriseName());
            deviceEntity.setAppId(AuthenticationHolder.getAppAuth().getAppId());
            if (null != type) {
                deviceEntity.setProductType(type.getProductType());
            }
            if (null != AuthenticationHolder.getAppAuth()) {
                deviceEntity.setAppId(AuthenticationHolder.getAppAuth().getAppId());
            }
            deviceEntity.setBindTime(String.valueOf(System.currentTimeMillis()));
            this.save(deviceEntity);
        } else {
            return BaseResultFactory.produceResult(Code.DEVICE_IS_BOUND, "设备Id已经被绑定");
        }
        DeviceDTO deviceDTO = new DeviceDTO();
        deviceDTO.setUserId(deviceEntity.getUserId());
        deviceDTO.setDeviceId(deviceEntity.getId());
        deviceDTO.setDeviceType(deviceEntity.getDeviceType());
        taskExecutor.execute(() -> {
            // 数据上链
            remoteDeviceBlockChainService.bindDevice(deviceDTO);
        });
        // 绑定日志记录
        BindLog bindLog = new BindLog().setImei(deviceVo.getImei()).setDeviceType(deviceVo.getDeviceType())
                .setPlatform(null != deviceVo.getPlatform() ? deviceVo.getPlatform().name() : DevicePlatform.UHOME.name())
                .setBindType(true);
        bindLogRepo.save(bindLog);
        return BaseResultFactory.produceResult(Code.SUCCESS, deviceEntity.getId());
    }

    @Override
    @Transactional
    public BaseResult unbind(String imei) {
        DeviceEntity deviceEntity = this.getDeviceWithAuthByImei(imei);
        if (ObjectUtils.isEmpty(deviceEntity)) {
            return BaseResultFactory.produceResult(Code.DEVICE_NOT_EXIST, "设备不存在");
        }
        // 解除设备关联
        if(doUnRelete(deviceEntity)){
            return BaseResultFactory.produceResult(Code.SUCCESS, "设备解绑成功");
        }else{
            return BaseResultFactory.produceEmptyResult(Code.NEED_RELETE_AHEAD);
        }
    }

    /**
     * 洗衣机解绑的时候要求 如果有关联，需要先解除关联，再解绑洗衣机
     * 加液泵解绑的时候要求 解除洗衣机的关联，并解绑加液泵
     * @param deviceEntity
     */
    private boolean doUnRelete(DeviceEntity deviceEntity){
        String productType = deviceEntity.getProductType().name();
        PumpWashRel pumpWashRel;
        boolean unBindflag = false;
        // 如果是加液泵，解除洗衣机的关联，并解绑加液泵
        if(StringUtils.equals(productType,ProductType.LIQUID_PUMP.name())){
            pumpWashRel = pumpWashRelRepo.findByPumpImei(deviceEntity.getImei());
            if(pumpWashRel != null){
                unBindflag = true;
                this.unRelete(pumpWashRel.getWashImei(),pumpWashRel.getPumpImei());
            }else{
                this.unRelete(null,deviceEntity.getImei());
            }
        }else{
            // 如果是洗衣机，如果有关联 需要先解除关联;没有关联直接解绑
            pumpWashRel = pumpWashRelRepo.findByWashImei(deviceEntity.getImei());
            if(pumpWashRel != null){
                log.warn("该{}洗衣机存在关联{}，请先解除加液泵的关联，再解绑洗衣机",pumpWashRel.getWashImei(),pumpWashRel.getPumpImei());
                return false;
            }else{
                this.unRelete(deviceEntity.getImei(),null);
            }
        }
        if(!unBindflag){
            // 解除设备绑定
            this.doUnBindDevice(deviceEntity);
        }
        return true;
    }

    private void doUnBindDevice(DeviceEntity deviceEntity) {
        log.info("解绑设备并上链");
        DeviceDTO deviceDTO = new DeviceDTO();
        deviceDTO.setUserId(deviceEntity.getUserId());
        deviceDTO.setDeviceId(deviceEntity.getId());
        deviceDTO.setDeviceType(deviceEntity.getDeviceType());
        taskExecutor.execute(() -> {
            // 数据上链
            remoteDeviceBlockChainService.unbindDevice(deviceDTO);
        });
        this.unbind(deviceEntity);
    }

    @Override
    @Transactional
    public BaseResult<?> unbindAll(Set<String> imeiSet, String userId) {
        if (CollectionUtils.isEmpty(imeiSet)) {
            return BaseResultFactory.produceEmptyResult(Code.SUCCESS);
        }
        List<DeviceEntity> deviceEntities;
        if (AuthenticationHolder.isAdmin()) {
            // 管理员
            deviceEntities = deviceEntityRepo.findByUserIdAndImeiIn(userId, imeiSet);
        } else {
            // 非管理员
            deviceEntities = deviceEntityRepo.findByUserIdAndImeiIn(AuthenticationHolder.getUserId(), imeiSet);
        }
        Set<String> imeiSetInDB = deviceEntities.stream().map(DeviceEntity::getImei).collect(Collectors.toSet());
        // 找不到的先放到结果集里
        Map<String, String> resMap = imeiSet.stream()
                .filter(imei -> !imeiSetInDB.contains(imei))
                .collect(Collectors.toMap(Function.identity(), v -> "设备不存在"));
        // 对找到的进行解绑
        deviceEntities.forEach(this::unbind);
        // 对找到的设备解除关联
        deviceEntities.forEach(d -> {
            this.doUnRelete(d);
        });
        return BaseResultFactory.produceResult(Code.SUCCESS, resMap);
    }

    @Override
    @Transactional
    public BaseResult<?> unbindAll(String userId) {
        List<DeviceEntity> deviceEntities;
        if (AuthenticationHolder.isAdmin()) {
            // 管理员
            deviceEntities = deviceEntityRepo.findByUserId(userId);
        } else {
            // 非管理员
            deviceEntities = deviceEntityRepo.findByUserId(AuthenticationHolder.getUserId());
        }
        deviceEntities.forEach(this::unbind);
        deviceEntities.forEach(d -> {
            this.doUnRelete(d);
        });
        return BaseResultFactory.produceEmptyResult(Code.SUCCESS);
    }

    @Override
    @Transactional
    public BaseResult<?> unbindAllByKeyword(String value, String userId) {
        DeviceQueryDTO deviceQueryDTO;
        if (AuthenticationHolder.isAdmin()) {
            // 管理员
            deviceQueryDTO = new DeviceQueryDTO(value, userId);
        } else {
            // 非管理员
            deviceQueryDTO = new DeviceQueryDTO(value, AuthenticationHolder.getUserId());
        }
        Specification<DeviceEntity> spec = deviceDao.getSpec(deviceQueryDTO);
        List<DeviceEntity> deviceEntities = deviceEntityRepo.findAll(spec);
        deviceEntities.forEach(this::unbind);
        deviceEntities.forEach(d -> {
            this.doUnRelete(d);
        });
        return BaseResultFactory.produceEmptyResult(Code.SUCCESS);
    }

    private void unbind(DeviceEntity deviceEntity) {
        // 解绑日志
        BindLog bindLog = new BindLog().setImei(deviceEntity.getImei()).setDeviceType(deviceEntity.getDeviceType())
                .setPlatform(null != deviceEntity.getPlatform() ? deviceEntity.getPlatform().name() : DevicePlatform.UHOME.name())
                .setBindType(false);
        bindLogRepo.save(bindLog);

        String userId = deviceEntity.getUserId();
        deviceEntity.setBindTime(null);
        deviceEntity.setUserId(null);
        deviceEntity.setFirstInteractionTime(null);
        deviceEntity.setLastInteractionTime(null);
        deviceEntity.setProjectId(null);
        deviceEntity.setAppId(null);
        deviceEntity.setSchoolId(null);
        deviceEntity.setWashPointId(null);
        deviceEntity.setConcretePositionId(null);
        deviceEntity.setWashPointName(null);
        deviceEntity.setBelongMerchantName(null);
        deviceEntity.setIsUsing(true);
        deviceEntity.setBrandId(null);
        deviceEntity.setBrandName(null);
        deviceEntity.setServiceTimeId(null);
        deviceEntity.setModeSetId(null);
        deviceEntity.setQrCode(null);
        deviceEntity.setMachineNo(null);
        deviceEntity.setAddress(null);
        deviceEntity.setDeviceName(null);
        deviceEntity.setDeviceNumber(null);
        deviceEntity.setDevicePlace(null);
        deviceEntityRepo.save(deviceEntity);
        // 发消息
        messageService.create(deviceEntity.getId(), userRoleService.getAnyAdminUserId(), userId,
                MsgType.DEVICE_UNBIND, new ArrayList<>(), Arrays.asList(deviceEntity.getDevicePlace(), deviceEntity.getImei(), "您的设备已被解绑"));
    }

    @Override
    public BaseResult<PageResult<DeviceListVO>> queryDeviceList(Pageable pageable, DeviceQueryDTO deviceQueryDTO) throws BusinessException {
        PageResult<DeviceListVO> result = deviceDao.findByCondition(pageable, deviceQueryDTO);
        return BaseResultFactory.produceResult(Code.SUCCESS, result);
    }

    @Override
    public PageResult<DeviceListVO> queryBorrowerDeviceList(Pageable pageable, String borrowerId) {
        StringBuilder pageSqlBuilder = new StringBuilder("SELECT d.* FROM device d WHERE EXISTS (SELECT p.id FROM loan l" +
                " JOIN project p ON l.id=p.loan_id WHERE p.id=d.project_id AND l.borrower_id=:borrowerId AND l.lender_id=:lenderId" +
                " AND l.STATUS !='CREATING') limit :start,:end");
        StringBuilder totalNumBuilder = new StringBuilder("SELECT count(d.id) FROM device d WHERE EXISTS (SELECT p.id FROM loan l " +
                " JOIN project p ON l.id=p.loan_id WHERE p.id=d.project_id AND l.borrower_id=:borrowerId  AND l.lender_id=:lenderId AND l.STATUS !='CREATING')");
        Query pageQuery = entityManager.createNativeQuery(pageSqlBuilder.toString(), DeviceEntity.class);
        pageQuery.setParameter("borrowerId", borrowerId);
        pageQuery.setParameter("lenderId", AuthenticationHolder.getUserId());
        pageQuery.setParameter("start", pageable.getPageSize() * pageable.getPageNumber());
        pageQuery.setParameter("end", pageable.getPageSize());
        List<DeviceEntity> deviceEntities = pageQuery.getResultList();
        List<DeviceListVO> deviceListVOList = deviceDao.toDeviceListVO(deviceEntities);
        PageResult<DeviceListVO> pageResult = new PageResult<>();
        pageResult.setElements(deviceListVOList);

        Query totalNumQuery = entityManager.createNativeQuery(totalNumBuilder.toString());
        totalNumQuery.setParameter("borrowerId", borrowerId);
        totalNumQuery.setParameter("lenderId", AuthenticationHolder.getUserId());
        List<BigInteger> countResList = totalNumQuery.getResultList();
        pageResult.setTotalSize(countResList.get(0).longValue());
        return pageResult;
    }

    @Override
    public DeviceEntity getDeviceWithAuthByImei(String imei) {
        DeviceEntity deviceEntity;

        if (true){
            deviceEntity = deviceEntityRepo.findByImei(imei);
        }else    if (StringUtils.isNotBlank(WechatUserHolder.get())// 微信支付回调触发
                || AuthenticationHolder.isAdmin()// 管理员
                || (null != AuthenticationHolder.getUserAuth()// 客户触发的
                && AuthenticationHolder.getUserAuth().getRoleTypes().contains(RoleType.CUSTOMER.name()))) {
            deviceEntity = deviceEntityRepo.findByImei(imei);
        } else {
            if (null != AuthenticationHolder.getAppAuth() || AuthenticationHolder.getUserAuth().getRoleTypes().contains(RoleType.PLATFORM_MERCHANT.name())) {
                // 平台商家
                deviceEntity = deviceEntityRepo.findByUserIdAndImei(AuthenticationHolder.getUserId(), imei);
            } else {
                // 运营型商家
                UserEntity userMerchant = userRelService.getUserMerchant(AuthenticationHolder.getUserId());
                if (AuthenticationHolder.getUserAuth().getViewAllData()) {
                    // 能查询所有数据
                    deviceEntity = deviceEntityRepo.findByUserIdAndImei(userMerchant.getId(), imei);
                } else {
                    // 只能查询子账号数据
                    Set<String> allSub = userRelService.getDescendantIds(AuthenticationHolder.getUserId());
                    allSub.add(AuthenticationHolder.getUserId());
                    deviceEntity = deviceEntityRepo.findByImeiAndCreatorIdIn(imei, allSub);
                }
            }
        }
        if (Objects.isNull(deviceEntity)) {
            throw new BusinessException(Code.NODATA);
        }
        return deviceEntity;
    }

    @Override
    public DeviceEntity getDeviceWithoutAuthByImei(String imei) {
        DeviceEntity deviceEntity = deviceEntityRepo.findByImei(imei);
        if (Objects.isNull(deviceEntity)) {
            throw new BusinessException(Code.NODATA);
        }
        return deviceEntity;
    }

    @Override
    public DeviceEntity getDeviceWithoutAuthByMachineNo(String machineNo) {
        DeviceEntity deviceEntity = deviceEntityRepo.findByMachineNo(machineNo);
        if (Objects.isNull(deviceEntity)) {
            throw new BusinessException(Code.NODATA);
        }
        return deviceEntity;
    }

    @Override
    public BaseResult<PageResult<DeviceStatisticsVo>> groupCountByUser(Pageable pageable, String value) {
        return deviceDao.groupCountByUser(pageable, value);
    }

    @Override
    public BaseResult<?> countAll(String userId) {
        Long count;
        if (AuthenticationHolder.isAdmin()) {
            if (StringUtils.isBlank(userId)) {
                count = deviceEntityRepo.count();
            } else {
                count = deviceEntityRepo.countByUserId(userId);
            }
        } else {
            count = deviceEntityRepo.countByUserId(AuthenticationHolder.getUserId());
        }
        return BaseResultFactory.produceResult(Code.SUCCESS, count);
    }

    @Override
    public DeviceDetailVO detail(String id) {
        DeviceEntity deviceEntity;
        if (AuthenticationHolder.isAdmin() || AuthenticationHolder.getUserAuth().getRoleTypes().contains(RoleType.FINANCIAL_INSTITUTIONS.name())) {
            deviceEntity = deviceEntityRepo.findById(id).orElse(deviceEntityRepo.findByImei(id));
        } else {
            deviceEntity = deviceEntityRepo.findByUserIdAndId(AuthenticationHolder.getUserId(), id);
            if(deviceEntity == null){
                deviceEntity = deviceEntityRepo.findByUserIdAndImei(AuthenticationHolder.getUserId(),id);
            }
        }
        if (null == deviceEntity) {
            throw new BusinessException(Code.NODATA, "设备不存在");
        }
        DeviceDetailVO deviceDetailVo = new DeviceDetailVO();
        BeanUtil.copyProperties(deviceEntity, deviceDetailVo);
        // 当id为设备Id时，查询不到关联信息，当id为设备Imei时，可以查询到设备关联信息
        PumpWashRel byWashImei = pumpWashRelRepo.findByWashImei(id);
        if(byWashImei == null){
            byWashImei = pumpWashRelRepo.findByWashImei(deviceDetailVo.getImei());
        }
        deviceDetailVo.setReleteImei(byWashImei != null ? byWashImei.getPumpImei() : null);
        ProjectEntity project = deviceEntity.getProject();
        if (null != project) {
            deviceDetailVo.setProjectId(project.getId());
            deviceDetailVo.setProjectName(project.getName());
        }
        if (StringUtils.isNotBlank(deviceEntity.getUserId())) {
            userEntityRepo.findById(deviceEntity.getUserId()).ifPresent(userEntity -> {
                deviceDetailVo.setUsername(userEntity.getUsername());
            });
        }
        return deviceDetailVo;
    }

    @Override
    public List<DeviceExportVO> export(Set<String> imeiSet, String userId) {
        List<DeviceEntity> deviceEntities;
        if (AuthenticationHolder.isAdmin()) {
            if (StringUtils.isBlank(userId)) {
                deviceEntities = deviceEntityRepo.findByImeiIn(imeiSet);
            } else {
                deviceEntities = deviceEntityRepo.findByUserIdAndImeiIn(userId, imeiSet);
            }
        } else {
            deviceEntities = deviceEntityRepo.findByUserIdAndImeiIn(AuthenticationHolder.getUserId(), imeiSet);
        }
        return entityToExportVO(deviceEntities);
    }

    @Override
    public List<DeviceExportVO> exportByKeyword(String value, String userId) {
        DeviceQueryDTO deviceQueryDTO;
        if (AuthenticationHolder.isAdmin()) {
            // 管理员
            deviceQueryDTO = new DeviceQueryDTO(value, userId);
        } else {
            // 非管理员
            deviceQueryDTO = new DeviceQueryDTO(value, null);
        }
        Specification<DeviceEntity> spec = deviceDao.getSpec(deviceQueryDTO);
        List<DeviceEntity> deviceEntities = deviceEntityRepo.findAll(spec);
        return entityToExportVO(deviceEntities);
    }

    @Override
    public List<DeviceExportVO> export(String userId) {
        List<DeviceEntity> deviceEntities;
        if (AuthenticationHolder.isAdmin()) {
            // 管理员
            if (StringUtils.isBlank(userId)) {
                deviceEntities = deviceEntityRepo.findAll((x, y, z) -> null);
            } else {
                deviceEntities = deviceEntityRepo.findByUserId(userId);
            }
        } else {
            // 非管理员
            deviceEntities = deviceEntityRepo.findByUserId(AuthenticationHolder.getUserId());
        }
        return entityToExportVO(deviceEntities);
    }

    private List<DeviceExportVO> entityToExportVO(List<DeviceEntity> deviceEntities) {
        if (CollectionUtils.isEmpty(deviceEntities)) {
            return new ArrayList<>();
        }
        Set<String> userIdSet = deviceEntities.stream().map(DeviceEntity::getUserId).collect(Collectors.toSet());
        List<UserEntity> userEntities = userEntityRepo.findByIdIn(userIdSet);
        Map<String, String> userIdNameMap = userEntities.stream().collect(Collectors.toMap(UserEntity::getId, UserEntity::getUsername));

        return deviceEntities.stream().map(deviceEntity -> {
            DeviceExportVO deviceExportVO = new DeviceExportVO();
            BeanUtils.copyProperties(deviceEntity, deviceExportVO, "bindTime", "lastInteractionTime");
            if (null != deviceEntity.getBindTime()) {
                deviceExportVO.setBindTime(new Date(Long.parseLong(deviceEntity.getBindTime())));
            }
            if (null != deviceEntity.getLastInteractionTime()) {
                deviceExportVO.setLastInteractionTime(new Date(Long.parseLong(deviceEntity.getLastInteractionTime())));
            }
            ProjectEntity project = deviceEntity.getProject();
            if (null != project) {
                deviceExportVO.setProjectName(project.getName());
            }
            deviceExportVO.setUsername(userIdNameMap.get(deviceEntity.getUserId()));
            if (null != deviceEntity.getProductType()) {
                deviceExportVO.setProductType(deviceEntity.getProductType().getDesc());
            }
            return deviceExportVO;
        }).collect(Collectors.toList());
    }

    @Override
    public List<BorrowerDeviceExportVO> fiExportBorrowerDevice(String borrowerId, String projectId) {
        StringBuilder pageSqlBuilder = new StringBuilder("SELECT d.* FROM device d WHERE EXISTS (SELECT p.id FROM project p" +
                " JOIN loan l ON l.id=p.loan_id WHERE l.borrower_id=:borrowerId AND l.lender_id=:lenderId AND l.status !='CREATING' AND p.id=d.project_id)");
        if (StringUtils.isNotBlank(projectId)) {
            pageSqlBuilder.append(" and d.project_id=:projectId");
        }
        Query pageQuery = entityManager.createNativeQuery(pageSqlBuilder.toString(), DeviceEntity.class);
        pageQuery.setParameter("borrowerId", borrowerId);
        pageQuery.setParameter("lenderId", AuthenticationHolder.getUserId());
        if (StringUtils.isNotBlank(projectId)) {
            pageQuery.setParameter("projectId", projectId);
        }
        List<DeviceEntity> deviceEntities = pageQuery.getResultList();
        return this.toBorrowerDeviceExportVO(deviceEntities);
    }

    @Override
    public List<BorrowerDeviceExportVO> adminExportBorrowerDevice(String borrowerId, String projectId) {
        DeviceQueryDTO deviceQueryDTO = new DeviceQueryDTO();
        deviceQueryDTO.setUserId(borrowerId);
        deviceQueryDTO.setProjectId(projectId);
        Specification<DeviceEntity> spec = deviceDao.getSpec(deviceQueryDTO);
        List<DeviceEntity> deviceEntities = deviceEntityRepo.findAll(spec);
        return this.toBorrowerDeviceExportVO(deviceEntities);
    }

    private List<BorrowerDeviceExportVO> toBorrowerDeviceExportVO(List<DeviceEntity> deviceEntities) {
        return deviceEntities.stream().map(deviceEntity -> {
            BorrowerDeviceExportVO deviceExportVO = new BorrowerDeviceExportVO();
            BeanUtils.copyProperties(deviceEntity, deviceExportVO, "lastInteractionTime");
            if (null != deviceEntity.getLastInteractionTime()) {
                deviceExportVO.setLastInteractionTime(new Date(Long.parseLong(deviceEntity.getLastInteractionTime())));
            }
            return deviceExportVO;
        }).collect(Collectors.toList());
    }

    @Override
    public Long countInteractedDevices(String userId) {
        userId = AuthenticationHolder.isAdmin() ? userId : AuthenticationHolder.getUserId();
        return deviceEntityRepo.countByUserIdAndLastInteractionTimeNotNull(userId);
    }

    @Override
    public Long countInteractedDevices() {
        if (!AuthenticationHolder.isAdmin()) {
            throw new BusinessException(Code.APP_PERMISSION_DENIED);
        }
        return deviceEntityRepo.countByLastInteractionTimeNotNull();
    }

    @Override
    public Long countTodayInteractedDevices(String userId) {
        String lastInteractionTimeBegin = Long.toString(System.currentTimeMillis() - BaseConstant.MILLI_SECONDS_A_DAY);
        userId = AuthenticationHolder.isAdmin() ? userId : AuthenticationHolder.getUserId();
        return deviceEntityRepo.countByUserIdAndLastInteractionTimeGreaterThanEqual(userId, lastInteractionTimeBegin);
    }

    @Override
    public Long countBorrowerInteractedDevices(String borrowerId) {
        StringBuilder sqlBuilder = new StringBuilder("select count(d.id) from device d where exists(" +
                " select p.id from project p join loan l on l.id=p.loan_id where l.borrower_id=:borrowerId and l.lender_id=:lenderId" +
                " and l.status!='CREATING' and d.project_id=p.id) and d.last_interaction_time is not null");
        Query nativeQuery = entityManager.createNativeQuery(sqlBuilder.toString());
        nativeQuery.setParameter("borrowerId", borrowerId);
        nativeQuery.setParameter("lenderId", AuthenticationHolder.getUserId());
        BigInteger res = (BigInteger) QueryUtil.getSingleResult(nativeQuery);
        if (null == res) {
            res = BigInteger.ZERO;
        }
        return res.longValue();
    }

    @Override
    public Long countBorrowerTodayInteractedDevices(String borrowerId) {
        String lastInteractionTimeBegin = Long.toString(System.currentTimeMillis() - BaseConstant.MILLI_SECONDS_A_DAY);
        StringBuilder sqlBuilder = new StringBuilder("select count(d.id) from device d where exists(" +
                " select p.id from project p join loan l on l.id=p.loan_id where l.borrower_id=:borrowerId and l.lender_id=:lenderId" +
                " and l.status!='CREATING' and d.project_id=p.id) and d.last_interaction_time >=:lastInteractionTimeBegin");
        Query nativeQuery = entityManager.createNativeQuery(sqlBuilder.toString());
        nativeQuery.setParameter("lastInteractionTimeBegin", lastInteractionTimeBegin);
        nativeQuery.setParameter("borrowerId", borrowerId);
        nativeQuery.setParameter("lenderId", AuthenticationHolder.getUserId());
        BigInteger res = (BigInteger) QueryUtil.getSingleResult(nativeQuery);
        if (null == res) {
            res = BigInteger.ZERO;
        }
        return res.longValue();
    }

    @Override
    public Long countProjectInteractedDevices(String projectId) {
        StringBuilder sqlBuilder = new StringBuilder("select count(d.id) from device d where d.project_id=:projectId" +
                " and d.last_interaction_time is not null");
        Query nativeQuery = entityManager.createNativeQuery(sqlBuilder.toString());
        nativeQuery.setParameter("projectId", projectId);
        BigInteger res = (BigInteger) QueryUtil.getSingleResult(nativeQuery);
        if (null == res) {
            res = BigInteger.ZERO;
        }
        return res.longValue();
    }

    @Override
    public Long countProjectTodayInteractedDevices(String projectId) {
        String lastInteractionTimeBegin = Long.toString(System.currentTimeMillis() - BaseConstant.MILLI_SECONDS_A_DAY);
        StringBuilder sqlBuilder = new StringBuilder("select count(d.id) from device d where d.project_id=:projectId" +
                " and d.last_interaction_time >=:lastInteractionTimeBegin");
        Query nativeQuery = entityManager.createNativeQuery(sqlBuilder.toString());
        nativeQuery.setParameter("projectId", projectId);
        nativeQuery.setParameter("lastInteractionTimeBegin", lastInteractionTimeBegin);
        BigInteger res = (BigInteger) QueryUtil.getSingleResult(nativeQuery);
        if (null == res) {
            res = BigInteger.ZERO;
        }
        return res.longValue();
    }

    @Override
    public BorrowerDeviceDetailVO borrowerDeviceDetail(String id) {
        if (!AuthenticationHolder.isAdmin()) {
            DeviceEntity deviceEntity = deviceEntityRepo.findById(id).orElseThrow(() -> new BusinessException(Code.NODATA));

            ProjectEntity project = deviceEntity.getProject();
            if (null == project) {
                throw new BusinessException(Code.NODATA);
            }
            LoanEntity loanEntity = loanEntityRepo.findById(project.getLoanId()).orElseThrow(() -> new BusinessException(Code.NODATA));
            if (loanEntity.getStatus().equals(LoanStatus.CREATING) || !loanEntity.getLenderId().equals(AuthenticationHolder.getUserId())) {
                throw new BusinessException(Code.PERMISSION_DENIED);
            }
        }
        DeviceDetailVO detail = this.detail(id);
        BorrowerDeviceDetailVO borrowerDeviceDetailVO = new BorrowerDeviceDetailVO();
        BeanUtil.copyProperties(detail, borrowerDeviceDetailVO);
        String lastInteractionTime = detail.getLastInteractionTime();
        if (StringUtils.isBlank(lastInteractionTime)) {
            borrowerDeviceDetailVO.setStatus(DeviceStatus.ABNORMAL);
        } else {
            long diffMilliSeconds = System.currentTimeMillis() - Long.parseLong(detail.getLastInteractionTime());
            if (diffMilliSeconds < BaseConstant.MILLI_SECONDS_A_DAY) {
                // 过去24小时内有与平台发生过交互即判定为正常
                borrowerDeviceDetailVO.setStatus(DeviceStatus.NORMAl);
            } else {
                borrowerDeviceDetailVO.setStatus(DeviceStatus.ABNORMAL);
            }
        }
        return borrowerDeviceDetailVO;
    }

    @Override
    public void unbindProject(String projectId) {
        List<DeviceEntity> deviceEntities = deviceEntityRepo.findAllByProjectId(projectId);
        if (!CollectionUtils.isEmpty(deviceEntities)) {
            for (DeviceEntity deviceEntity : deviceEntities) {
                deviceEntity.setProjectId(null);
            }
            deviceEntityRepo.saveAll(deviceEntities);
        }
    }

    @Override
    public String getDeviceType(String imei) {
        DeviceEntity byImei = deviceEntityRepo.findByImei(imei);
        if (null == byImei) {
            return null;
        }
        return byImei.getDeviceType();
    }

    @Override
    public boolean deviceLocked(String deviceId) {
        String lock = stringRedisTemplate.opsForValue().get(RedisKey.DeviceOperate.DEVICE_LOCK_KEY_PREFIX + deviceId);
        if (ObjectUtil.isNull(lock)) {
            return false;
        }
        // 如果不是自己占用的
        return !AuthenticationHolder.getUserId().equals(lock);
    }

    @Override
    public boolean lockDevice(String deviceId) {
        return lock0(deviceId, 120);
    }

    @Override
    public boolean lockDevice(String deviceId, int time) {
        return lock0(deviceId, time);
    }

    @Override
    public void lockTransfer(String imei, String userId, int time) {
        stringRedisTemplate.opsForValue().set(RedisKey.DeviceOperate.DEVICE_LOCK_KEY_PREFIX + imei, userId, time, TimeUnit.MINUTES);
    }

    private boolean lock0(String deviceId, int expireTime) {
        Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(RedisKey.DeviceOperate.DEVICE_LOCK_KEY_PREFIX + deviceId, AuthenticationHolder.getUserId(), expireTime, TimeUnit.MINUTES);
        if (BooleanUtils.isTrue(success)) {
            // 锁成功
            return true;
        }
        // 锁失败
        // 如果是自己占用的
        if (AuthenticationHolder.getUserId().equals(stringRedisTemplate.opsForValue().get(RedisKey.DeviceOperate.DEVICE_LOCK_KEY_PREFIX + deviceId))) {
            stringRedisTemplate.expire(RedisKey.DeviceOperate.DEVICE_LOCK_KEY_PREFIX + deviceId, expireTime, TimeUnit.MINUTES);
            return true;
        }
        return false;
    }

    @Override
    public boolean unlockDevice(String deviceId) {
        if (!Objects.isNull(stringRedisTemplate.opsForValue().get(RedisKey.DeviceOperate.DEVICE_LOCK_KEY_PREFIX + deviceId))) {
            if (AuthenticationHolder.getUserId().equals(stringRedisTemplate.opsForValue().get(RedisKey.DeviceOperate.DEVICE_LOCK_KEY_PREFIX + deviceId))) {
                stringRedisTemplate.delete(RedisKey.DeviceOperate.DEVICE_LOCK_KEY_PREFIX + deviceId);
                return true;
            }
        }
        return false;
    }

    @Override
    public DeviceInfoVO getDeviceInfo(String machineNo) {
        DeviceEntity deviceEntity = deviceEntityRepo.findByMachineNo(machineNo);
        if (Objects.isNull(deviceEntity)) {
            throw new BusinessException(Code.ERROR, "没有找到设备机器码为：" + machineNo + "的设备！");
        }
        DeviceInfoVO result = new DeviceInfoVO();
        BeanUtil.copyProperties(deviceEntity, result);
        result.setDeviceAddress(this.parseConcretePosition(deviceEntity));
        if (null != deviceEntity.getDeviceServiceTimeEntity()) {
            DeviceServiceTimeEntity deviceServiceTimeEntity = deviceEntity.getDeviceServiceTimeEntity();
            result.setStartTime(deviceServiceTimeEntity.getStartTime());
            result.setEndTime(deviceServiceTimeEntity.getEndTime());
        }
        if (StringUtils.isNotBlank(deviceEntity.getWashPointId())) {
            Optional<WashPointEntity> washPointEntityOptional = washPointEntityRepo.findById(deviceEntity.getWashPointId());
            washPointEntityOptional.ifPresent(washPointEntity -> {
                result.setServicePhone(washPointEntity.getServicePhone());
            });
        }
        return result;
    }

    @Override
    public boolean deviceAvailable4Customer(DeviceEntity deviceEntity) {
        if (StringUtils.isNotBlank(deviceEntity.getUserId()) && deviceEntity.getIsUsing() && deviceEntity.getOnline()
                && deviceEntity.getStatus() == DeviceStatus.NOT_RUNNING && null != deviceEntity.getPlatform()
                && CollectionUtils.isEmpty(deviceAlarmRepo.findAllByDeviceIdAndValid(deviceEntity.getId(), true))
        ) {
            // 已绑定运营商、启用、上线、未运行、有所属平台且无告警、在服务时间，可用
            return true;
        } else {
            // 否则不可用
            List<OrderEntity> processes = orderEntityRepo.findByCustomerIdAndDeviceIdAndOrderStatus(AuthenticationHolder.getUserId(), deviceEntity.getId(), OrderStatus.IN_PROGRESS);
            if (CollectionUtil.isNotEmpty(processes)) {
                throw new BusinessException(Code.HAVE_IN_PROGRESS_ORDER);
            }
            return false;
        }
    }

    /**
     * 设备是否在服务时间
     *
     * @param serviceTimeId
     * @return
     */
    public boolean inServiceTime(String serviceTimeId) {
        if (StringUtils.isBlank(serviceTimeId)) {
            return true;
        }
        Optional<DeviceServiceTimeEntity> serviceTimeEntityOpt = deviceServiceTimeEntityRepo.findById(serviceTimeId);
        if (!serviceTimeEntityOpt.isPresent()) {
            return true;
        }
        DeviceServiceTimeEntity serviceTimeEntity = serviceTimeEntityOpt.get();
        LocalTime now = LocalTime.now();
        if (now.isBefore(serviceTimeEntity.getStartTime()) || now.isAfter(serviceTimeEntity.getEndTime())) {
            return false;
        }
        return true;
    }

    @Override
    public Specification<DeviceEntity> customerAvailableDeviceSpec(String washPointId) {
        List alarmDeviceIdList = entityManager.createNativeQuery("select distinct device_id from device_alarm where valid=true").getResultList();
        return (root, query, cb) -> {
            List<Predicate> predicates = Lists.newArrayList();
            // 已绑定运营商、启用、上线、未运行、有所属平台且无告警
            if (CollectionUtil.isNotEmpty(alarmDeviceIdList)) {
                predicates.add(cb.not(root.get("id").in(alarmDeviceIdList)));
            }
            if (StringUtils.isNotBlank(washPointId)) {
                predicates.add(cb.equal(root.get("washPointId"), washPointId));
            }
            predicates.add(cb.isNotNull(root.get("userId")));
            predicates.add(cb.equal(root.get("isUsing"), true));
            predicates.add(cb.equal(root.get("online"), true));
            predicates.add(cb.isNotNull(root.get("platform")));

            Predicate[] preArr = new Predicate[predicates.size()];
            return cb.and(predicates.toArray(preArr));
        };
    }

    @Override
    public PageResult<DeviceListVO> queryDeviceListByWashPoint(Pageable pageable, String washPointId) {
        Page<DeviceEntity> page = deviceEntityRepo.findAll((root, query, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            predicates.add(criteriaBuilder.equal(root.get("washPointId"), washPointId));
            if (!AuthenticationHolder.isAdmin()) {
                //todo:有个子账号的问题
                predicates.add(criteriaBuilder.equal(root.get("userId"), AuthenticationHolder.getUserId()));
            }
            Predicate[] preArr = new Predicate[1];
            return criteriaBuilder.and(predicates.toArray(preArr));
        }, pageable);

        return new PageResult<>(page.getTotalElements(), deviceDao.toDeviceListVO(page.getContent()));
    }

    @Override
    public PageResult queryDeviceListForOperPlat(Pageable pageable, Set<String> washPointIds, String keyword, String deviceType, String productType, Integer deviceStatus, String userId) {
        Page<DeviceEntity> page = deviceEntityRepo.findAll(assembleDeviceSpecification(washPointIds, keyword, deviceType, productType, deviceStatus, userId,null,null), pageable);
        PageResult pg = new PageResult(page.getTotalElements(), switchOperVO(page.getContent()));
        return pg;
    }

    @Override
    public PageResult queryDeviceListForOperPlat(Pageable pageable, Set<String> washPointIds, String keyword,
                                                 String deviceType, String productType, Integer deviceStatus, String userId,String province,String city) {
        Page<DeviceEntity> page = deviceEntityRepo.findAll(assembleDeviceSpecification(washPointIds, keyword, deviceType, productType, deviceStatus, userId,province,city), pageable);
        PageResult pg = new PageResult(page.getTotalElements(), switchOperVO(page.getContent()));
        return pg;
    }

    private Specification<DeviceEntity> assembleDeviceSpecification(Set<String> washPointIds, String keyword, String deviceType, String productType, Integer deviceStatus, String userId,String province,String city) {
        Boolean viewAllData = AuthenticationHolder.getUserAuth().getViewAllData();
        return (root, query, criteriaBuilder) -> {
            List<Predicate> predicates = Lists.newArrayList();
            //数据权限控制
            if (!AuthenticationHolder.isAdmin()) {
                if(AuthenticationHolder.isAgent()){
                    List<UserEntity> allByCreatorId = userEntityRepo.findAllByCreatorId(AuthenticationHolder.getUserId());
                    predicates.add(root.get("userId").in(allByCreatorId.stream().map(UserEntity::getId).collect(Collectors.toList())));
                }else {
                    //如果不是管理员做权限控制
                    //获取根账户
                    UserEntity rootUser = userRelService.getUserMerchant(AuthenticationHolder.getUserId());
                    predicates.add(criteriaBuilder.equal(root.get("userId"), rootUser.getId()));
                    if (!viewAllData) {
                        Set<String> allSub = userRelService.getDescendantIds(AuthenticationHolder.getUserId());
                        allSub.add(AuthenticationHolder.getUserId());
                        predicates.add(root.get("creatorId").in(allSub));
                    }
                }

            } else {
                if (StringUtils.isNotBlank(userId)) {
                    criteriaBuilder.equal(root.get("userId"), userId);
                }
            }
            // 添加运营商不为空的判断
            predicates.add(criteriaBuilder.isNotNull(root.get("belongMerchantName")));
            if (CollectionUtil.isNotEmpty(washPointIds)) {
                CriteriaBuilder.In<Object> in = criteriaBuilder.in(root.get("washPointId"));
                for (String id : washPointIds) {
                    in.value(id);
                }
                predicates.add(in);
            }
            if(StringUtils.isNotBlank(province)){
                Subquery<WashPointEntity> subQuery = query.subquery(WashPointEntity.class);
                Root<WashPointEntity> subRoot = subQuery.from(WashPointEntity.class);
                List<Predicate> likes = Lists.newArrayList();
                likes.add(criteriaBuilder.like(subRoot.get("area"), "%" + province + "%"));
                if( StringUtils.isNotBlank(city)){
                    likes.add(criteriaBuilder.and(criteriaBuilder.like(subRoot.get("area"), "%" + city + "%")));
                }
                subQuery.where(likes.toArray(new Predicate[likes.size()]));
                subQuery.select(subRoot.get("id"));
                CriteriaBuilder.In<Object> in = criteriaBuilder.in(root.get("washPointId"));
                in.value(subQuery);
                predicates.add(in);
            }
            if (StringUtils.isNotBlank(keyword)) {
                List<Predicate> likes = Lists.newArrayList();
                likes.add(criteriaBuilder.like(root.get("deviceName"), "%"+keyword+"%"));
                likes.add(criteriaBuilder.like(root.get("belongMerchantName"), "%"+keyword+"%"));
                likes.add(criteriaBuilder.like(root.get("creatorName"), "%"+keyword+"%"));
                likes.add(criteriaBuilder.like(root.get("simCard"), "%"+keyword+"%"));
                Predicate[] like = new Predicate[likes.size()];
                predicates.add(criteriaBuilder.or(likes.toArray(like)));
            }
            if (StringUtils.isNotBlank(productType)) {
                CriteriaBuilder.In<Object> in = criteriaBuilder.in(root.get("productType"));
                String[] productTypes = productType.split(",");
                for (int i = 0; i < productTypes.length; i++) {
                    in.value(ProductType.valueOf(productTypes[i]));
                }
                predicates.add(in);
            }
            if (StringUtils.isNotBlank(deviceType)) {
                predicates.add(criteriaBuilder.equal(root.get("deviceType"), deviceType));
            }
            if (deviceStatus != null && deviceStatus > 0) {
                Subquery<DeviceAlarmEntity> subQuery = query.subquery(DeviceAlarmEntity.class);
                Root<DeviceAlarmEntity> subRoot = subQuery.from(DeviceAlarmEntity.class);
                subQuery.where(criteriaBuilder.equal(subRoot.get("valid"), true), criteriaBuilder.equal(root.get("id"), subRoot.get("deviceId")));
                subQuery.select(subRoot.get("id"));
                //如果是故障
                if (DeviceFilterStatus.OFF_LINE.getCode().equals(deviceStatus)) {
                    List<Predicate> bugs = Lists.newArrayList();
                    bugs.add(criteriaBuilder.equal(root.get("online"), false));
                    bugs.add(criteriaBuilder.exists(subQuery));
                    Predicate[] bugsArr = new Predicate[bugs.size()];
                    predicates.add(criteriaBuilder.or(bugs.toArray(bugsArr)));
                } else if (DeviceFilterStatus.NORMAL.getCode().equals(deviceStatus)) {
                    //如果是正常
                    List<Predicate> normal = Lists.newArrayList();
                    normal.add(criteriaBuilder.equal(root.get("online"), true));
                    normal.add(criteriaBuilder.equal(root.get("isUsing"), true));
                    normal.add(criteriaBuilder.not(criteriaBuilder.exists(subQuery)));
                    Predicate[] normalArr = new Predicate[normal.size()];
                    predicates.add(criteriaBuilder.and(normal.toArray(normalArr)));
                } else {
                    //如果是停用
                    List<Predicate> stopUse = Lists.newArrayList();
                    stopUse.add(criteriaBuilder.equal(root.get("online"), true));
                    stopUse.add(criteriaBuilder.equal(root.get("isUsing"), false));
                    stopUse.add(criteriaBuilder.not(criteriaBuilder.exists(subQuery)));
                    Predicate[] stopUseArr = new Predicate[stopUse.size()];
                    predicates.add(criteriaBuilder.and(stopUse.toArray(stopUseArr)));
                }
            }
            Predicate[] predicateArr = new Predicate[predicates.size()];
            return criteriaBuilder.and(predicates.toArray(predicateArr));

        };
    }


    @Override
    public List<DeviceExportForOperVO> exportDeviceForOperPlat(Set<String> ids, String keyword, String deviceType, Integer deviceStatus, Boolean all, String userId) {
        if (CollectionUtil.isNotEmpty(ids)) {
            return switchExport4Oper(deviceEntityRepo.findAllByIdIn(ids));
        } else {
            return switchExport4Oper(deviceEntityRepo.findAll(assembleDeviceSpecification(ids, keyword, deviceType, "", deviceStatus, userId,null,null)));
        }
    }

    @Override
    public void startOrEndingDevice(String id, Boolean isUsing) {
        DeviceEntity deviceEntity = deviceEntityRepo.findById(id).orElseThrow(() -> new BusinessException(Code.ERROR, "不存在id为:" + id + "的设备"));
        if (AuthenticationHolder.isAdmin()) {
            deviceEntity.setUpdateTime(String.valueOf(System.currentTimeMillis()));
        } else {
            String currentId = AuthenticationHolder.getUserId();
            if (AuthenticationHolder.getUserAuth().getViewAllData()) {
                UserEntity merchant = userRelService.getUserMerchant(currentId);
                if (Objects.isNull(deviceEntityRepo.findByUserIdAndId(merchant.getId(), deviceEntity.getId()))) {
                    throw new BusinessException(Code.ERROR, "您没有该设备的操作权限");
                }
            } else {
                Set<String> allSub = userRelService.getDescendantIds(currentId);
                allSub.add(currentId);
                if (Objects.isNull(deviceEntityRepo.findByIdAndCreatorIdIn(deviceEntity.getId(), allSub))) {
                    throw new BusinessException(Code.ERROR, "您没有该设备的操作权限");
                }
            }
        }
        deviceEntity.setIsUsing(isUsing);
        if(isUsing){
            deviceEntity.setStatus(DeviceStatus.NOT_RUNNING);
        }else{
            deviceEntity.setStatus(DeviceStatus.PAUSE);
        }

        deviceEntityRepo.save(deviceEntity);
    }

    @Override
    public void batchStartOrEndingDevice(BatchSwitchDeviceVO batchSwitchDeviceVO) {
        List<DeviceEntity> deviceList = deviceEntityRepo.findAllByIdIn(batchSwitchDeviceVO.getDeviceIds());
        deviceList.forEach(x -> {
            x.setIsUsing(batchSwitchDeviceVO.getIsUsing());
            if(batchSwitchDeviceVO.getIsUsing()){
                x.setStatus(DeviceStatus.NOT_RUNNING);
            }else{
                x.setStatus(DeviceStatus.PAUSE);
            }
        });
        deviceEntityRepo.saveAll(deviceList);
    }

    @Override
    public DeviceDetailForOperVO operQueryDeviceDetail(String id) {
        DeviceEntity deviceEntity = deviceEntityRepo.findById(id).orElse(deviceEntityRepo.findByImei(id));
        if(deviceEntity == null){
            throw new BusinessException(Code.NODATA);
        }
        DeviceDetailForOperVO copy = BeanCopyUtil.copy(deviceEntity, DeviceDetailForOperVO.class);
        copy.setStartTime(Optional.ofNullable(deviceEntity.getDeviceServiceTimeEntity()).map(x -> x.getStartTime()).orElse(null));
        copy.setEndTime(Optional.ofNullable(deviceEntity.getDeviceServiceTimeEntity()).map(x -> x.getEndTime()).orElse(null));
        copy.setProductType(Objects.isNull(deviceEntity.getProductType()) ? "" : deviceEntity.getProductType().name());
        copy.setDeviceStatus(BooleanUtils.isTrue(deviceEntity.getIsUsing()) ? (BooleanUtils.isTrue(deviceEntity.getOnline()) ? DeviceFilterStatus.NORMAL.name()
                : DeviceFilterStatus.OFF_LINE.name()) : DeviceFilterStatus.STOP_USE.name());
        if (StringUtils.isNotBlank(copy.getModeSetId())) {
            DeviceModeSetDetailVO detail = deviceModeSetService.detail(copy.getModeSetId());
            copy.setModeSetName(detail.getName());
        }
        if (StringUtils.isNotBlank(copy.getConcretePositionId())) {
            Optional<WashPointConcretePositionEntity> washPointConcretePositionEntityRepoById = washPointConcretePositionEntityRepo.findById(copy.getConcretePositionId());
            if(washPointConcretePositionEntityRepoById.isPresent()){
                WashPointConcretePositionEntity concretePositionEntity = washPointConcretePositionEntityRepoById.get();
                copy.setBuilding(concretePositionEntity.getBuilding());
                copy.setFloor(concretePositionEntity.getFloor());
                copy.setRoom(concretePositionEntity.getRoom());
            }
        }
        // 当id为设备Id时，查询不到关联信息，当id为设备Imei时，可以查询到设备关联信息
        PumpWashRel byWashImei = pumpWashRelRepo.findByWashImei(id);
        if(byWashImei == null){
            byWashImei = pumpWashRelRepo.findByWashImei(copy.getImei());
        }
        if(byWashImei != null){
            copy.setReleteImei(byWashImei.getPumpImei());
        }
        // 查询补货列表，获取洗衣液消毒液余量
        Replenish replenish = replenishService.findByImei(id);
        if(replenish != null){
            copy.setLaundryDetergent(replenish.getLaundryDetergent());
            copy.setDisinfectant(replenish.getDisinfectant());
            if(StringUtils.isNotBlank(copy.getWashPointId())){
                Optional<LiquidCapacitySetting> setting = liquidCapacitySettingRepo.findById(copy.getWashPointId());
                if(setting.isPresent()){
                    copy.checkOperation(setting.get().getLThreshold(),setting.get().getDThreshold(),replenish.getReplenishStatus().getCode());
                    copy.setlDefaultSize(setting.get().getLCapacity());
                    copy.setdDefaultSize(setting.get().getDCapacity());
                }else{
                    copy.setlDefaultSize(ReplenishServiceImpl.LAUNDRY_DETERGENT_CAPACITY_THRESHOLD);
                    copy.setdDefaultSize(ReplenishServiceImpl.DISINFECTANT_CAPACITY_THRESHOLD);
                    copy.checkOperation(ReplenishServiceImpl.LAUNDRY_DETERGENT_CAPACITY_THRESHOLD,
                            ReplenishServiceImpl.DISINFECTANT_CAPACITY_THRESHOLD,replenish.getReplenishStatus().getCode());
                }

            }
        }
        copy.setBelongOperatorName(deviceEntity.getBelongMerchantName());
        return copy;
    }

    @Override
    public PageResult operQueryInvokeHistory(Pageable pageable, String id) {
        DeviceEntity deviceEntity = deviceEntityRepo.findById(id).orElseThrow(() -> new BusinessException(Code.NODATA));


        StringBuilder sb = new StringBuilder("select i.create_time,o.order_no,o.order_name,i.result_status from device_order o left join" +
                " invoke_history i on o.id = i.order_id where o.order_status='ALREADY_COMPLETE' and i.device_id='").append(deviceEntity.getId()).append("'")
                .append(" order by i.create_time desc")
                .append(" limit :start,:end");
        Query nativeQuery = entityManager.createNativeQuery(sb.toString());
        nativeQuery.setParameter("start", pageable.getPageNumber() * pageable.getPageSize());
        nativeQuery.setParameter("end", pageable.getPageSize());
        List<Object[]> resultList = nativeQuery.getResultList();
        List<InvokeHistoryForOperListVO> collect = resultList.stream().map(x -> {
            String operateTime = (String) x[0];
            String orderNo = (String) x[1];
            String orderName = (String) x[2];
            String result = (String) x[3];
            InvokeHistoryForOperListVO invokeHistoryForOperListVO = new InvokeHistoryForOperListVO();
            invokeHistoryForOperListVO.setOperateTime(operateTime);
            invokeHistoryForOperListVO.setOrderNo(orderNo);
            invokeHistoryForOperListVO.setModel(orderName);
            invokeHistoryForOperListVO.setResult(result);
            if (ProductType.WASHING_MACHINE.equals(deviceEntity.getProductType())) {
                invokeHistoryForOperListVO.setUseFunction("洗涤");
            } else if (ProductType.DRY_MACHINE.equals(deviceEntity.getProductType())) {
                invokeHistoryForOperListVO.setUseFunction("烘干");
            } else if (ProductType.WASHING_SHOES_MACHINE.equals(deviceEntity.getProductType())) {
                invokeHistoryForOperListVO.setUseFunction("洗鞋");
            }else if (ProductType.LIQUID_PUMP.equals(deviceEntity.getProductType())){
                invokeHistoryForOperListVO.setUseFunction("加液");
                invokeHistoryForOperListVO.setModel("默认");
            }
            return invokeHistoryForOperListVO;
        }).collect(Collectors.toList());

        StringBuilder countSql = new StringBuilder("select count(*) from device_order o left join" +
                " invoke_history i on o.id = i.order_id where o.order_status='ALREADY_COMPLETE' and o.device_id='").append(deviceEntity.getId()).append("'");
        Query countQ = entityManager.createNativeQuery(countSql.toString());
        BigInteger count = QueryUtil.getSingleResult(countQ, BigInteger.ZERO);
        PageResult pg = new PageResult(count.longValue(), collect);
        return pg;
    }

    @Override
    public void operEditDevice(EditDeviceForOperVO editDeviceForOperVO) {
        DeviceEntity deviceEntity = deviceEntityRepo.findById(editDeviceForOperVO.getId()).orElseThrow(() -> new BusinessException(Code.NODATA));
        deviceEntity.setWashPointId(editDeviceForOperVO.getWashPointId());
        WashPointEntity washPointEntity = washPointEntityRepo.findById(editDeviceForOperVO.getWashPointId()).orElseThrow(() -> new BusinessException(Code.NODATA));
        deviceEntity.setWashPointName(washPointEntity.getName());
        deviceEntity.setConcretePositionId(editDeviceForOperVO.getConcretePositionId());
        if (StringUtils.isNotBlank(editDeviceForOperVO.getConcretePositionId())) {
            WashPointConcretePositionEntity concrete = washPointConcretePositionEntityRepo.findByIdAndWashPointId(editDeviceForOperVO.getConcretePositionId(), washPointEntity.getId());
            if (Objects.isNull(concrete)) {
                throw new BusinessException(Code.NODATA);
            }
        }
        deviceEntity.setBrandId(editDeviceForOperVO.getBrandId());
        DeviceBrandEntity entity = deviceBrandEntityRepo.findById(editDeviceForOperVO.getBrandId()).orElseThrow(() -> new BusinessException(Code.NODATA));
        deviceEntity.setDeviceName(editDeviceForOperVO.getDeviceName());
        deviceEntity.setBrandName(entity.getName());
        deviceEntity.setModeSetId(editDeviceForOperVO.getModeSetId());
        deviceEntity.setProductType(ProductType.valueOf(editDeviceForOperVO.getProductType()));
        deviceEntity.setDeviceType(DeviceType.valueOf(editDeviceForOperVO.getDeviceType().replace("-", "_")).getName());
        deviceEntity.setServiceTimeId(editDeviceForOperVO.getServiceTimeId());
        deviceEntityRepo.save(deviceEntity);
        //查找 关联的加液器设备
        if(ProductType.WASHING_MACHINE.equals(deviceEntity.getProductType())){
            PumpWashRel byWashImei = pumpWashRelRepo.findByWashImei(deviceEntity.getImei());
            if(byWashImei != null){
                DeviceEntity pumpDeviceEntity = deviceEntityRepo.findByImei(byWashImei.getPumpImei());
                pumpDeviceEntity.setServiceTimeId(editDeviceForOperVO.getServiceTimeId());
                pumpDeviceEntity.setWashPointId(editDeviceForOperVO.getWashPointId());
                pumpDeviceEntity.setWashPointName(washPointEntity.getName());
                pumpDeviceEntity.setConcretePositionId(editDeviceForOperVO.getConcretePositionId());
                deviceEntityRepo.save(pumpDeviceEntity);
            }
        }
    }

    private List<DeviceExportForOperVO> switchExport4Oper(List<DeviceEntity> list) {
        DateTimeFormatter df = DateTimeFormatter.ofPattern("hh:mm:ss");
        return list.stream().map(x -> {
            DeviceExportForOperVO def = BeanCopyUtil.copy(x, DeviceExportForOperVO.class);
            def.setServiceTime(x.getDeviceServiceTimeEntity() == null ? "" : (x.getDeviceServiceTimeEntity().getStartTime().format(df) + "-" + x.getDeviceServiceTimeEntity().getEndTime().format(df)));
            if (StringUtils.isNotBlank(x.getModeSetId())) {
                DeviceModeSetDetailVO detail = deviceModeSetService.detail(x.getModeSetId());
                def.setChargeMode(detail.getModes().stream().map(x1 -> x1.getName() + ":" + x1.getPrice() + "元" + x1.getTime() + "分钟").collect(Collectors.joining(",")));
            }
            def.setProductType(x.getProductType() == null ? "" : x.getProductType().getDesc());
            return def;
        }).collect(Collectors.toList());
    }

    private List<DeviceListForOperVO> switchOperVO(List<DeviceEntity> list) {
        return list.stream().map(x -> {
            DeviceListForOperVO d = BeanCopyUtil.copy(x, DeviceListForOperVO.class);
            d.setProductType(Objects.isNull(x.getProductType()) ? "" : x.getProductType().getDesc());
            List<DeviceAlarmEntity> alarmList = deviceAlarmRepo.findAllByDeviceIdAndValid(x.getId(), true);
            if (CollectionUtil.isEmpty(alarmList)) {
                if (Objects.isNull(x.getOnline())) {
                    d.setDeviceStatus(DeviceFilterStatus.OFF_LINE.name());
                } else {
                    d.setDeviceStatus(x.getOnline() ? (x.getIsUsing() ? DeviceFilterStatus.NORMAL.name() : DeviceFilterStatus.STOP_USE.name()) : DeviceFilterStatus.OFF_LINE.name());
                }
            } else {
                d.setDeviceStatus(DeviceFilterStatus.OFF_LINE.name());
            }
            if (StringUtils.isNotBlank(x.getWashPointId())) {
                Optional<WashPointEntity> washPoint = washPointEntityRepo.findById(x.getWashPointId());
                if(washPoint.isPresent()){
                    d.setWashPointName(washPoint.get().getName());
                }
            }
            if (StringUtils.isNotBlank(x.getConcretePositionId())) {
                Optional<WashPointConcretePositionEntity> washPointConcretePositionEntityRepoById = washPointConcretePositionEntityRepo.findById(x.getConcretePositionId());
                if(washPointConcretePositionEntityRepoById.isPresent()){
                    WashPointConcretePositionEntity concretePositionEntity = washPointConcretePositionEntityRepoById.get();
                    d.setBuilding(concretePositionEntity.getBuilding());
                    d.setFloor(concretePositionEntity.getFloor());
                    d.setRoom(concretePositionEntity.getRoom());
                }
            }
            d.setDeviceTypeName(DeviceType.getDescByName(x.getDeviceType()));
            return d;
        }).collect(Collectors.toList());
    }

    @Override
    public void updateDeviceModeSetId(String oldId, String newId) {
        if (StringUtils.equals(oldId, newId)) {
        }
        List<DeviceEntity> byModeSetId = deviceEntityRepo.findByModeSetId(oldId);
        for (DeviceEntity deviceEntity : byModeSetId) {
            deviceEntity.setModeSetId(newId);
        }
        deviceEntityRepo.saveAll(byModeSetId);
    }

    @Override
    public OMDeviceStateStatisticsVO omDeviceStateStatistics(String productType, String deviceType, String preferWashPointId) {
        Boolean viewAllData = AuthenticationHolder.getUserAuth().getViewAllData();
        String currentId = AuthenticationHolder.getUserId();
        UserEntity userMerchant = userRelService.getUserMerchant(currentId);
        Set<String> descendantIds = userRelService.getDescendantIds(currentId);
        descendantIds.add(currentId);
        StringBuilder onlineAndNoFaultDeviceQuerySql = new StringBuilder("SELECT d.is_using,count(d.id) FROM device d " +
                " WHERE d.online=1 AND NOT EXISTS ( SELECT da.id FROM device_alarm da WHERE da.device_id=d.id AND da.valid=TRUE)");
        StringBuilder offlineOrFaultDeviceQuerySql = new StringBuilder("SELECT count(d.id) FROM device d WHERE (d.ONLINE=0 OR" +
                " EXISTS (SELECT da.id FROM device_alarm da WHERE da.device_id=d.id AND da.valid=TRUE))");
        if (viewAllData) {
            onlineAndNoFaultDeviceQuerySql.append(" AND d.user_id=:userId");
            offlineOrFaultDeviceQuerySql.append(" AND d.user_id=:userId");
        } else {
            onlineAndNoFaultDeviceQuerySql.append(" AND d.creator_id in :ids");
            offlineOrFaultDeviceQuerySql.append(" AND d.creator_id in :ids");
        }
        if (StringUtils.isNotBlank(productType)) {
            onlineAndNoFaultDeviceQuerySql.append(" AND product_type=:productType");
            offlineOrFaultDeviceQuerySql.append(" AND product_type=:productType");
        }
        if (StringUtils.isNotBlank(deviceType)) {
            onlineAndNoFaultDeviceQuerySql.append(" AND device_type=:deviceType");
            offlineOrFaultDeviceQuerySql.append(" AND device_type=:deviceType");
        }
        if (StringUtils.isNotBlank(preferWashPointId)) {
            onlineAndNoFaultDeviceQuerySql.append(" AND d.wash_point_id = :washPointId");
            offlineOrFaultDeviceQuerySql.append(" AND d.wash_point_id = :washPointId");
        }
        onlineAndNoFaultDeviceQuerySql.append(" GROUP BY d.is_using");
        Query onlineAndNoFaultDeviceQuery = entityManager.createNativeQuery(onlineAndNoFaultDeviceQuerySql.toString());
        Query offlineOrFaultDeviceQuery = entityManager.createNativeQuery(offlineOrFaultDeviceQuerySql.toString());
        if (viewAllData) {
            onlineAndNoFaultDeviceQuery.setParameter("userId", userMerchant.getId());
            offlineOrFaultDeviceQuery.setParameter("userId", userMerchant.getId());
        } else {
            onlineAndNoFaultDeviceQuery.setParameter("ids", descendantIds);
            offlineOrFaultDeviceQuery.setParameter("ids", descendantIds);
        }
        if (StringUtils.isNotBlank(productType)) {
            onlineAndNoFaultDeviceQuery.setParameter("productType", productType);
            offlineOrFaultDeviceQuery.setParameter("productType", productType);
        }
        if (StringUtils.isNotBlank(deviceType)) {
            onlineAndNoFaultDeviceQuery.setParameter("deviceType", deviceType);
            offlineOrFaultDeviceQuery.setParameter("deviceType", deviceType);
        }
        if (StringUtils.isNotBlank(preferWashPointId)) {
            onlineAndNoFaultDeviceQuery.setParameter("washPointId", preferWashPointId);
            offlineOrFaultDeviceQuery.setParameter("washPointId", preferWashPointId);
        }
        List<Object[]> resultList = onlineAndNoFaultDeviceQuery.getResultList();
        OMDeviceStateStatisticsVO omDeviceStateStatisticsVO = new OMDeviceStateStatisticsVO();
        for (Object[] objs : resultList) {
            Boolean isUsing = (Boolean) objs[0];
            BigInteger count = (BigInteger) objs[1];
            if (null == count) {
                count = BigInteger.ZERO;
            }
            if (BooleanUtils.isTrue(isUsing)) {
                omDeviceStateStatisticsVO.setNormalCount(count.longValue());
            } else {
                omDeviceStateStatisticsVO.setDisableCount(count.longValue());
            }
        }
        BigInteger offlineOrFaultDeviceCount = QueryUtil.getSingleResult(offlineOrFaultDeviceQuery, BigInteger.ZERO);
        omDeviceStateStatisticsVO.setFaultCount(offlineOrFaultDeviceCount.longValue());
        omDeviceStateStatisticsVO.setTotalCount(omDeviceStateStatisticsVO.getDisableCount() + omDeviceStateStatisticsVO.getFaultCount() + omDeviceStateStatisticsVO.getNormalCount());
        return omDeviceStateStatisticsVO;
    }

    @Override
    public void batchChangeWashPoint(Set<String> ids, String washPointId) {
        WashPointEntity washPointEntity = washPointEntityRepo.findById(washPointId).orElseThrow(() -> new BusinessException(Code.NODATA));
        Iterable<DeviceEntity> devices = deviceEntityRepo.findAllById(ids);
        devices.forEach(x -> {
            x.setWashPointId(washPointEntity.getId());
            x.setWashPointName(washPointEntity.getName());
            x.setConcretePositionId(null);
        });
        deviceEntityRepo.saveAll(devices);
    }

    @Override
    public void batchChangeDeviceMode(Set<String> ids, String deviceModeId) {
        if (CollectionUtil.isEmpty(ids)) {
            throw new BusinessException(Code.ERROR, "请先选择设备");
        }
        Iterable<DeviceEntity> devices = deviceEntityRepo.findAllById(ids);
        List<DeviceEntity> deviceEntities = Lists.newArrayList(devices);
        if (CollectionUtil.isEmpty(deviceEntities)) {
            throw new BusinessException(Code.ERROR, "没有找到相关设备");
        }
        if (deviceEntities.size() >= 1) {
            for (int i = 0; i < deviceEntities.size(); i++) {
                if (i >= 1) {
                    if (!deviceEntities.get(i).getDeviceType().equals(deviceEntities.get(i - 1).getDeviceType())) {
                        throw new BusinessException(Code.ERROR, "请保持设备列表设备类型一致");
                    }
                }
            }
        }
        deviceModeSetRepo.findByIdAndDeviceType(deviceModeId, deviceEntities.get(0).getDeviceType());
        devices.forEach(x -> x.setModeSetId(deviceModeId));
        deviceEntityRepo.saveAll(devices);
    }

    @Override
    public void deviceOnline(String imei) {
        DeviceEntity deviceEntity = deviceEntityRepo.findByImei(imei);
        if (Objects.isNull(deviceEntity)) {
            return;
        }
        if (!BooleanUtils.isTrue(deviceEntity.getOnline())) {
            deviceEntity.setOnline(true);
            deviceEntityRepo.save(deviceEntity);
        }
    }

    @Override
    public void deviceOffline(String imei) {
        DeviceEntity deviceEntity = deviceEntityRepo.findByImei(imei);
        if (Objects.isNull(deviceEntity)) {
            return;
        }
        if (BooleanUtils.isTrue(deviceEntity.getOnline())) {
            deviceEntity.setOnline(false);
            deviceEntityRepo.save(deviceEntity);
        }
    }

    @Override
    @Transactional
    public void updateDeviceIccid(String imei, String iccid) {
        if (StringUtils.isBlank(iccid)) {
            return;
        }
        DeviceEntity byIccid = deviceEntityRepo.findByIccid(iccid);
        if (null != byIccid && !byIccid.getImei().equals(imei)) {
            byIccid.setIccid(null);
            deviceEntityRepo.save(byIccid);
        }
        DeviceEntity deviceEntity = deviceEntityRepo.findByImei(imei);
        if (null != deviceEntity) {
            deviceEntity.setIccid(iccid);
            deviceEntityRepo.save(deviceEntity);
        }
    }

    @Override
    public void batchChangeServiceTime(Set<String> ids, String serviceTimeId) {
        if (CollectionUtil.isEmpty(ids)) {
            throw new BusinessException(Code.ERROR, "请先选择设备");
        }
        Iterable<DeviceEntity> devices = deviceEntityRepo.findAllById(ids);
        List<DeviceEntity> deviceEntities = Lists.newArrayList(devices);
        if (CollectionUtil.isEmpty(deviceEntities)) {
            throw new BusinessException(Code.ERROR, "没有找到相关设备");
        }
        if (deviceEntities.size() >= 1) {
            for (int i = 0; i < deviceEntities.size(); i++) {
                if (i >= 1) {
                    if (!deviceEntities.get(i).getWashPointId().equals(deviceEntities.get(i - 1).getWashPointId())) {
                        throw new BusinessException(Code.ERROR, "请保持设备归属洗衣点一致");
                    }
                }
            }
        }

        if (Objects.isNull(deviceServiceTimeEntityRepo.findByIdAndWashPointId(serviceTimeId, deviceEntities.get(0).getWashPointId()))) {
            throw new BusinessException(Code.ERROR, "在该洗衣点没有找到对应id的服务时间");
        }
        devices.forEach(x -> x.setServiceTimeId(serviceTimeId));
        deviceEntityRepo.saveAll(devices);
    }

    @Override
    public UnBindResultVO batchUnbindDeviceForDevOps(Set<String> ids) {
        if (CollectionUtil.isEmpty(ids)) {
            throw new BusinessException(Code.ERROR, "请选择要解绑的设备");
        }

        String currentId = AuthenticationHolder.getUserId();
        Boolean viewAllData = AuthenticationHolder.getUserAuth().getViewAllData();
        UserEntity userMerchant = userRelService.getUserMerchant(currentId);
        Set<String> descendantIds = userRelService.getDescendantIds(currentId);
        descendantIds.add(currentId);
        if (viewAllData) {
            return handleBatch(ids, deviceEntityRepo.findAllByUserIdAndIdIn(userMerchant.getId(), ids));
        } else {
            return handleBatch(ids, deviceEntityRepo.findAllByCreatorIdInAndIdIn(descendantIds, ids));
        }
    }

    @Override
    @Transactional
    public BaseResult addDeviceForDevOps(DeviceAddForChargeVO deviceAddForChargeVO) {
        DeviceEntity byImei = deviceEntityRepo.findByImei(deviceAddForChargeVO.getImei());
        if (!Objects.isNull(byImei)) {
            if (StringUtils.isNotBlank(byImei.getUserId())) {
                return BaseResultFactory.produceResult(Code.DEVICE_IS_BOUND, "设备Id已经被绑定");
            }
            byImei.setMachineNo(deviceAddForChargeVO.getMachineNo());
            byImei.setImei(deviceAddForChargeVO.getImei());
            byImei.setWashPointId(deviceAddForChargeVO.getWashPointId());
            WashPointEntity wp = washPointEntityRepo.findById(deviceAddForChargeVO.getWashPointId()).orElseThrow(() -> new BusinessException(Code.NODATA));
            byImei.setWashPointName(wp.getName());
            byImei.setConcretePositionId(deviceAddForChargeVO.getConcretePositionId());
            byImei.setDeviceName(deviceAddForChargeVO.getDeviceName());
            byImei.setBrandId(deviceAddForChargeVO.getBrandId());
            DeviceBrandEntity brand = deviceBrandEntityRepo.findById(deviceAddForChargeVO.getBrandId()).orElseThrow(() -> new BusinessException(Code.NODATA));
            byImei.setBrandName(brand.getName());
            byImei.setProductType(deviceAddForChargeVO.getProductType());
            byImei.setDeviceType(deviceAddForChargeVO.getDeviceType());
            byImei.setModeSetId(deviceAddForChargeVO.getModeSetId());
            byImei.setServiceTimeId(deviceAddForChargeVO.getServiceTimeId());
            byImei.setStatus(DeviceStatus.NOT_RUNNING);
            byImei.setIsUsing(true);
            if(byImei.getOnline() == null){
                byImei.setOnline(false);
            }
            String userId = AuthenticationHolder.getUserId();
            UserEntity userMerchant = userRelService.getUserMerchant(userId);
            byImei.setCreatorId(userId);
            byImei.setCreatorName(AuthenticationHolder.getUserAuth().getUsername());
            byImei.setUserId(userMerchant.getId());
            byImei.setBelongMerchantName(userMerchant.getEnterpriseName());
            byImei.setBindTime(String.valueOf(System.currentTimeMillis()));
            this.save(byImei);
        } else {
            DeviceEntity copy = BeanCopyUtil.copy(deviceAddForChargeVO, DeviceEntity.class);
            DeviceBrandEntity brand = deviceBrandEntityRepo.findById(deviceAddForChargeVO.getBrandId()).orElseThrow(() -> new BusinessException(Code.NODATA));
            copy.setBrandName(brand.getName());
            WashPointEntity wp = washPointEntityRepo.findById(deviceAddForChargeVO.getWashPointId()).orElseThrow(() -> new BusinessException(Code.NODATA));
            copy.setWashPointName(wp.getName());
            copy.setBindTime(String.valueOf(System.currentTimeMillis()));
            copy.setIsUsing(true);
            copy.setOnline(false);
            copy.setStatus(DeviceStatus.NOT_RUNNING);
            this.save(copy);
        }
        return BaseResultFactory.produceSuccess(deviceAddForChargeVO.getImei());
    }

    @Override
    public Boolean judgeDevice(String machineNo, String imei) {
        if (StringUtils.isNotBlank(machineNo)) {
            DeviceEntity byMachineNo = deviceEntityRepo.findByMachineNo(machineNo);
            if (Objects.nonNull(byMachineNo) && StringUtils.isNotBlank(byMachineNo.getUserId())) {
                return true;
            }
            return false;
        } else if (StringUtils.isNotBlank(imei)) {
            DeviceEntity byMachineNo = deviceEntityRepo.findByImei(imei);
            if (Objects.nonNull(byMachineNo) && StringUtils.isNotBlank(byMachineNo.getUserId())) {
                return true;
            }
            return false;
        } else {
            throw new BusinessException(Code.ERROR);
        }
    }


    private UnBindResultVO handleBatch(Set<String> deviceIds, List<DeviceEntity> allList) {
        List<String> unFindId = deviceIds.stream().filter(x -> !allList.stream().map(DeviceEntity::getId).collect(Collectors.toList()).contains(x)).collect(Collectors.toList());
        if (CollectionUtil.isNotEmpty(unFindId)) {
            throw new BusinessException(Code.ERROR, "以下设备您无权限解绑:" + unFindId.toString());
        }
        UnBindResultVO unBindResultVO = new UnBindResultVO();
        List<UnBindResultVO.Result> successList = Lists.newArrayList();
        List<UnBindResultVO.Result> failList = Lists.newArrayList();
        allList.forEach(x -> {
            try {
                this.doUnRelete(x);
                buildInnerRe(x, successList);
            } catch (Exception e) {
                log.info("设备解绑失败，id:{}", x.getId());
                buildInnerRe(x, failList);
            }
        });
        unBindResultVO.setSuccessList(successList);
        unBindResultVO.setFailList(failList);
        return unBindResultVO;
    }

    private void buildInnerRe(DeviceEntity x, List<UnBindResultVO.Result> list) {
        UnBindResultVO.Result re = new UnBindResultVO.Result();
        re.setDeviceName(x.getDeviceName());
        re.setDeviceTypeName(DeviceType.getDescByName(x.getDeviceType()));
        re.setId(x.getId());
        re.setWashPointName(x.getWashPointName());
        if (StringUtils.isNotBlank(x.getConcretePositionId())) {
            re.setConcretePositionName(washPointConcretePositionEntityRepo.findById(x.getConcretePositionId())
                    .map(x1 -> x1.getBuilding() + "号楼" + x1.getFloor() + "层" + x1.getRoom() + "室").orElse(""));
        }
        re.setConcretePositionName(this.parseConcretePosition(x));
        list.add(re);
    }

    @Override
    public String parseConcretePosition(DeviceEntity deviceEntity) {
        if (StringUtils.isBlank(deviceEntity.getConcretePositionId())) {
            return null;
        }
        return washPointConcretePositionEntityRepo.findById(deviceEntity.getConcretePositionId())
                .map(entity -> entity.getBuilding() + "号楼" + entity.getFloor() + "层" + entity.getRoom() + "室").orElse(null);
    }

    @Override
    @Transactional
    public void updateInteractionTime(String imei, String timestamp) {
        DeviceEntity deviceEntity = deviceEntityRepo.findByImei(imei);
        if (null == deviceEntity) {
            return;
        }
        if (StringUtils.isBlank(deviceEntity.getFirstInteractionTime())) {
            deviceEntity.setFirstInteractionTime(timestamp);
        }
        deviceEntity.setOnline(true);
        deviceEntity.setLastInteractionTime(timestamp);
        deviceEntityRepo.saveAndFlush(deviceEntity);
    }

    @Override
    public PageResult<ProdLineTestDeviceListVO> searchLineTestDeviceList(String imei, DeviceModuleStatus moduleStatus, String qrCode, Pageable pageable) {
        Specification<DeviceEntity> spec = (root, query, criteriaBuilder) -> {
            List<Predicate> predicateList = new ArrayList<>();

            if (StringUtils.isNotBlank(imei)) {
                predicateList.add(criteriaBuilder.like(root.get("imei"), "%" + imei + "%"));
            }
            if (StringUtils.isNotBlank(qrCode)) {
                predicateList.add(criteriaBuilder.equal(root.get("qrCode"), qrCode));
            }
            if (null != moduleStatus) {
                predicateList.add(criteriaBuilder.equal(root.get("moduleStatus"), moduleStatus));
            }

            Predicate[] predicates = new Predicate[predicateList.size()];
            predicateList.toArray(predicates);
            return criteriaBuilder.and(predicates);
        };
        Page<DeviceEntity> entityPage = deviceEntityRepo.findAll(spec, pageable);

        long totalElements = entityPage.getTotalElements();
        PageResult<ProdLineTestDeviceListVO> result = new PageResult<>();
        result.setElements(entityPage.getContent().stream().map(entity -> BeanCopyUtil.copy(entity, ProdLineTestDeviceListVO.class)).collect(Collectors.toList()));
        result.setTotalSize(totalElements);
        return result;
    }

    @Override
    public BaseResult<?> lineTestDevice(String deviceId, String code) {
        Optional<DeviceEntity> deviceOpt = deviceEntityRepo.findById(deviceId);
        if (!deviceOpt.isPresent()) {
            return BaseResultFactory.produceEmptyResult(Code.NODATA);
        }
        DeviceEntity deviceEntity = deviceOpt.get();
        if (DevicePlatform.ALIYUN_IOT != deviceEntity.getPlatform()) {
            return BaseResultFactory.produceEmptyResult(Code.DEVICE_NOT_AVAILABLE);
        }
        BaseResult<?> baseResult = verifyCodeService.verifyVerifyCode(VerifyType.DEVICE, deviceId, code);
        if (baseResult.getCode() == Code.SUCCESS.getCode()) {
            aliyunIOTDeviceOpService.doReset(deviceEntity);
        }
        deviceEntity.setModuleStatus(DeviceModuleStatus.TESTED);
        deviceEntity.setLineTestTime(System.currentTimeMillis() + "");
        deviceEntityRepo.save(deviceEntity);
        return baseResult;
    }

    @Override
    public DeviceEntity findByImei(String imei) {
        if (StringUtils.isBlank(imei)) {
            return null;
        }
        return deviceEntityRepo.findByImei(imei);
    }

    @Override
    @Transactional
    public DeviceEntity save(DeviceEntity deviceEntity) {
        DeviceEntity res;
        if (null == deviceEntity.getId()) {
            res = this.create(deviceEntity);
        } else {
            res = this.update(deviceEntity);
        }
        remoteBloomFilterService.addImei(deviceEntity.getImei());
        stringRedisTemplate.opsForSet().add(RedisKey.IMEI_SET, deviceEntity.getImei());
        this.asyncSyncOnlineStatus(deviceEntity);
        return res;
    }

    /**
     * 异步方式同步是否在线
     *
     * @param deviceEntity
     */
    private void asyncSyncOnlineStatus(DeviceEntity deviceEntity) {
        // 如果是优家平台的设备，且不在线，去查询一下设备状态
        if (null != deviceEntity.getPlatform() && DevicePlatform.UHOME == deviceEntity.getPlatform() && !BooleanUtils.isTrue(deviceEntity.getOnline())) {
            UserAuthentication userAuth = AuthenticationHolder.getUserAuth();
            AppAuthentication appAuth = AuthenticationHolder.getAppAuth();
            BaseConstant.SCHEDULED_EXECUTOR.execute(() -> {
                AuthenticationHolder.setUserAuth(userAuth);
                AuthenticationHolder.setAppAuth(appAuth);
                try {
                    this.syncOnlineStatus(deviceEntity);
                } catch (Exception e) {
                    log.error("同步优家平台设备：{}上线状态时异常：", deviceEntity.getId(), e);
                } finally {
                    AuthenticationHolder.clear();
                }
            });
        }
    }

    @Override
    public void syncOnlineStatus(DeviceEntity deviceEntity) {
        deviceEntity.setOnline(null);
        BaseResult<?> baseResult = uHomeDeviceOpService.doDeviceOnline(deviceEntity);
        // 查询成功才进行更新
        if (baseResult.getCode() == Code.SUCCESS.getCode()) {
            String data = (String) baseResult.getData();
            Boolean isOnline = JSONObject.parseObject(data).getBoolean("isOnline");
            deviceEntity.setOnline(isOnline);
        } else {
            deviceEntity.setOnline(false);
        }
        deviceEntityRepo.save(deviceEntity);
    }

    private DeviceEntity create(DeviceEntity deviceEntity) {
        String imei = deviceEntity.getImei();
        DeviceEntity byImei = deviceEntityRepo.findByImei(imei);
        if (null != byImei) {
            throw new BusinessException(Code.DATA_EXIST, "deviceId已存在");
        }
        String machineNo = deviceEntity.getMachineNo();
        DeviceEntity byMachineNo = deviceEntityRepo.findByMachineNo(machineNo);
        if (null != byMachineNo) {
            throw new BusinessException(Code.DATA_EXIST, "machineId已存在");
        }
        String qrCode = deviceEntity.getQrCode();
        if (StringUtils.isNotBlank(qrCode)) {
            DeviceEntity byQrCode = deviceEntityRepo.findByQrCode(qrCode);
            if (null != byQrCode) {
                throw new BusinessException(Code.DATA_EXIST, "qrCode已存在");
            }
        }
        return deviceEntityRepo.save(deviceEntity);
    }

    private DeviceEntity update(DeviceEntity deviceEntity) {
//        entityManager.detach(deviceEntity);
        String imei = deviceEntity.getImei();
        DeviceEntity byImei = deviceEntityRepo.findByImei(imei);
        if (null != byImei && !deviceEntity.getId().equals(byImei.getId())) {
            throw new BusinessException(Code.DATA_EXIST, "deviceId已存在");
        }
        String machineNo = deviceEntity.getMachineNo();
        DeviceEntity byMachineNo = deviceEntityRepo.findByMachineNo(machineNo);
        if (null != byMachineNo && !deviceEntity.getId().equals(byMachineNo.getId())) {
            throw new BusinessException(Code.DATA_EXIST, "machineId已存在");
        }
        String qrCode = deviceEntity.getQrCode();
        if (StringUtils.isNotBlank(qrCode)) {
            DeviceEntity byQrCode = deviceEntityRepo.findByQrCode(qrCode);
            if (null != byQrCode && !deviceEntity.getId().equals(byQrCode.getId())) {
                throw new BusinessException(Code.DATA_EXIST, "qrCode已存在");
            }
        }
//        deviceEntity.setVersion(deviceEntity.getVersion()+1);
//        entityManager.merge(deviceEntity);
        return deviceEntityRepo.save(deviceEntity);
    }

    @Override
    public BaseResult<?> deviceImportForFactory(MultipartFile file) {
        Map<String, String> dictByType = sysDictService.getDictByType(SysDictType.ALIYUN_IOT_PRODUCT_KEY);
        Optional<String> productKeyOpt = dictByType.keySet().stream().findFirst();
        if (!productKeyOpt.isPresent()) {
            return BaseResultFactory.produceResult(Code.NODATA, "productKey不存在");
        }
        String productKey = productKeyOpt.get();
        ImportParams importParams = new ImportParams();
        importParams.setHeadRows(1);
        importParams.setImportFields(FactoryDeviceImportDTO.FIELDS);
        try (InputStream inputStream = file.getInputStream();) {
            List<FactoryDeviceImportDTO> list = ExcelImportUtil.importExcel(inputStream, FactoryDeviceImportDTO.class, importParams);
            if (CollectionUtils.isEmpty(list)) {
                return BaseResultFactory.produceResult(Code.ERROR, "无数据");
            }
            return this.doDeviceImportForFactory(productKey, list);
        } catch (Exception e) {
            log.error("导入出厂设备异常：", e);
            return BaseResultFactory.produceEmptyResult(Code.ERROR);
        }
    }

    /**
     * 这个方法会有两处调用： 1解除关联 2 解除绑定
     * 洗衣机解绑的时候要求 如果有关联，需要先解除关联，再解绑洗衣机
     * 加液泵解绑的时候要求 解除洗衣机的关联，并解绑加液泵
     * 加液泵解除关联的时候，要求解绑加液泵,只有加液器有解除关联的按钮
     * @param washImei
     * @param pumpImei
     * @return
     */
    @Override
    @Transactional
    public BaseResult unRelete(String washImei, String pumpImei) {
        log.info("解除设备关联{}，{}",washImei,pumpImei);
        // 解除设备关联
        if(StringUtils.isNotBlank(washImei) && StringUtils.isNotBlank(pumpImei)){
            PumpWashRel pumpWashRel = pumpWashRelRepo.findAllByPumpImeiAndWashImei(pumpImei, washImei);
            if(pumpWashRel != null){
                pumpWashRel.setBindStatus(false);
                pumpWashRelRepo.save(pumpWashRel);
                // 补货清单删除
                replenishService.deleteReplenish(StringUtils.isBlank(pumpImei) ? pumpWashRel.getPumpImei() : pumpImei);
            }
            // 获取加液泵设备
            DeviceEntity pumpDevice = getDeviceWithAuthByImei(pumpImei);
            this.doUnBindDevice(pumpDevice);
        }
        return BaseResultFactory.produceSuccess();
    }

    @Override
    public BaseResult relete(ReleteVO releteVo) {
        log.info("{}洗衣机关联加液器{}",releteVo.getWashImei(),releteVo.getImei());
        // 如果关联的设备并非液体泵
        if(!ProductType.LIQUID_PUMP.equals(releteVo.getProductType())){
            log.error("关联设备并非液体泵");
            return BaseResultFactory.produceResult(Code.RELETE_DEVICE_ERROR,"关联设备并非加液器");
        }
        PumpWashRel pumpWashRel = pumpWashRelRepo.findOneByProperty("washImei",releteVo.getWashImei());
        // 找不到相关设备的关联信息 或者 已经解除关联了
        if(pumpWashRel == null){
            pumpWashRel = pumpWashRelRepo.findOneByProperty("pumpImei",releteVo.getImei());
            // 加液器和洗衣机 1对1关联
            if(pumpWashRel == null){
                pumpWashRel = new PumpWashRel();
            }else{
                if(pumpWashRel.isBindStatus()){
                    return BaseResultFactory.produceResult(Code.RELETE_ABOVE_ONCE,null);
                }
            }
        }else{
            if(pumpWashRel.isBindStatus()){
                return BaseResultFactory.produceResult(Code.RELETE_ABOVE_ONCE,null);
            }
        }
        DeviceAddForChargeVO deviceVO = new DeviceAddForChargeVO();
        BeanUtil.copyProperties(releteVo, deviceVO, CopyOptions.create().setIgnoreNullValue(true));
        BaseResult<?> bindResult = this.addDeviceForDevOps(deviceVO);
        // 如果设备已经被绑定了 或者绑定成功
        if(bindResult.getCode() == Code.DEVICE_IS_BOUND.getCode()
                || bindResult.getCode() == Code.SUCCESS.getCode()){
            // 加液泵上线的时候 添加入补货表中
            replenishService.onlineUpdate(deviceVO.getDeviceType(),deviceVO.getImei());
        }
        // 设置液体泵设备码
        pumpWashRel.setPumpImei(releteVo.getImei());
        pumpWashRel.setWashImei(releteVo.getWashImei());
        pumpWashRel.setBindStatus(true);
        pumpWashRelRepo.save(pumpWashRel);
        return BaseResultFactory.produceSuccess();
    }

    private BaseResult<?> doDeviceImportForFactory(String productKey, List<FactoryDeviceImportDTO> list) {
        List<String> imeiList = new ArrayList<>(list.size());
        List<DeviceEntity> deviceEntities = new ArrayList<>(list.size());
        // 存库
        for (FactoryDeviceImportDTO factoryDeviceImportDTO : list) {
            imeiList.add(factoryDeviceImportDTO.getImei());
            DeviceEntity deviceEntity = DeviceEntity.builder()
                    .imei(factoryDeviceImportDTO.getImei())
                    .machineNo(factoryDeviceImportDTO.getImei())
                    .simCard(factoryDeviceImportDTO.getSimCard())
                    .isUsing(false)
                    .online(true)
                    .status(DeviceStatus.NOT_RUNNING)
                    .moduleStatus(DeviceModuleStatus.NOT_TEST)
                    .platform(DevicePlatform.ALIYUN_IOT)
                    .build();
            deviceEntities.add(deviceEntity);
        }
        deviceEntityRepo.saveAll(deviceEntities);
        // 去阿里云创建设备
        boolean result = true;
        // 每次最多创建1000个设备
        for (List<String> imeiSlice : Lists.partition(imeiList, 1000)) {
            Long applyId = aliyunIotService.batchCheckDeviceNames(productKey, imeiSlice);
            log.info("批量创建设备，批量检查阿里云IOT设备名。productKey: {}, applyId: {}", productKey, applyId);
            boolean res = aliyunIotService.batchRegisterDeviceWithApplyId(productKey, applyId);
            log.info("批量创建设备，批量创建阿里云IOT设备。productKey: {}, applyId: {}, result: {}", productKey, applyId, res);
            result &= res;
        }
        if (result) {
            return BaseResultFactory.produceSuccess();
        } else {
            return BaseResultFactory.produceEmptyResult(Code.ERROR);
        }
    }

    @Transactional
    public void updateDeviceStatus(String status,String imei){
        try {
            deviceEntityRepo.updateDeviceStatus(status,imei);
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}
